/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <random>
#include "optimizer/ir/inst.h"
#include "optimizer/ir/basicblock.h"
#include "optimizer/optimizations/if_conversion.h"
#include "optimizer/optimizations/lowering.h"
#include "optimizer/optimizations/regalloc/reg_alloc.h"

#include "libpandabase/macros.h"
#include "gtest/gtest.h"
#include "unit_ecma_test.h"
#include "optimizer/code_generator/codegen.h"
#include "vixl_exec_module.h"
#include "runtime/include/coretypes/tagged_value.h"
#include "ir-dyn-base-types.h"

namespace ark::compiler {
class CodegenEcmaTest : public GraphTest {
public:
    CodegenEcmaTest() : execModule_(GetAllocator(), GetGraph()->GetRuntime()) {}
    ~CodegenEcmaTest() override = default;

    NO_COPY_SEMANTIC(CodegenEcmaTest);
    NO_MOVE_SEMANTIC(CodegenEcmaTest);

    void TestCompareAnyType(AnyBaseType anyType, uint64_t val, bool expectedValue);
    void TestAnyTypeCheck(AnyBaseType anyType, uint64_t val);
    template <typename T>
    void CastAnyTypeValue(AnyBaseType boxedType, TaggedValue boxedValue, T expectedUnboxedValue);
    template <typename T>
    void CastValueToAnyType(T unboxedValue, AnyBaseType boxedType, uint64_t expectedBoxedValue);
    template <typename T>
    void CheckReturnValue(Graph *graph, T expectedValue);

private:
    VixlExecModule execModule_;
};

static bool RunCodegen(Graph *graph)
{
    return graph->RunPass<Codegen>();
}

// NOLINTBEGIN(readability-magic-numbers)
template <typename T>
void CodegenEcmaTest::CheckReturnValue(Graph *graph, T expectedValue)
{
    SetNumVirtRegs(0);
    ASSERT_TRUE(RegAlloc(graph));
    ASSERT_TRUE(RunCodegen(graph));

    auto codeEntry = reinterpret_cast<char *>(graph->GetData().Data());
    auto codeExit = codeEntry + graph->GetData().Size();  // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)

    ASSERT(codeEntry != nullptr && codeExit != nullptr);

    execModule_.SetInstructions(codeEntry, codeExit);
    execModule_.SetDump(false);

    execModule_.Execute();
    auto rv = execModule_.GetRetValue<T>();
    EXPECT_EQ(rv, expectedValue);
}

void CodegenEcmaTest::TestCompareAnyType(AnyBaseType anyType, uint64_t val, bool expectedValue)
{
    auto graph = GetGraph();
    graph->SetDynamicMethod();
    GRAPH(graph)
    {
        CONSTANT(0, val).any();

        BASIC_BLOCK(2, -1)
        {
            INST(2, Opcode::CompareAnyType).b().AnyType(anyType).Inputs(0);
            INST(3, Opcode::Return).b().Inputs(2);
        }
    }

    CheckReturnValue(graph, expectedValue);
}

TEST_F(CodegenEcmaTest, CompareAnyTypeInstBoolTrue1)
{
    TestCompareAnyType(AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE, TaggedValue::VALUE_TRUE, true);
}

TEST_F(CodegenEcmaTest, CompareAnyTypeInstBoolTrue2)
{
    TestCompareAnyType(AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE, TaggedValue::VALUE_FALSE, true);
}

TEST_F(CodegenEcmaTest, CompareAnyTypeInstBoolFalse)
{
    TestCompareAnyType(AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE, TaggedValue::VALUE_TRUE + 1, false);
}

TEST_F(CodegenEcmaTest, CompareAnyTypeInstDoubleTrue)
{
    TestCompareAnyType(AnyBaseType::ECMASCRIPT_DOUBLE_TYPE, TaggedValue::DOUBLE_ENCODE_OFFSET, true);
}

TEST_F(CodegenEcmaTest, CompareAnyTypeInstDoubleFalse1)
{
    TestCompareAnyType(AnyBaseType::ECMASCRIPT_DOUBLE_TYPE, TaggedValue::TAG_INT, false);
}

TEST_F(CodegenEcmaTest, CompareAnyTypeInstDoubleFalse2)
{
    TestCompareAnyType(AnyBaseType::ECMASCRIPT_DOUBLE_TYPE, TaggedValue::TAG_OBJECT + 1, false);
}

TEST_F(CodegenEcmaTest, CompareAnyTypeInstIntTrue)
{
    TestCompareAnyType(AnyBaseType::ECMASCRIPT_INT_TYPE, TaggedValue::TAG_INT, true);
}

TEST_F(CodegenEcmaTest, CompareAnyTypeInstIntFalse)
{
    TestCompareAnyType(AnyBaseType::ECMASCRIPT_INT_TYPE, TaggedValue::DOUBLE_ENCODE_OFFSET, false);
}

TEST_F(CodegenEcmaTest, CompareAnyTypeInstNullTrue)
{
    TestCompareAnyType(AnyBaseType::ECMASCRIPT_NULL_TYPE, TaggedValue::VALUE_NULL, true);
}

TEST_F(CodegenEcmaTest, CompareAnyTypeInstNullFalse)
{
    TestCompareAnyType(AnyBaseType::ECMASCRIPT_NULL_TYPE, TaggedValue::TAG_INT, false);
}

TEST_F(CodegenEcmaTest, CompareAnyTypeInstObjectTrue)
{
    TestCompareAnyType(AnyBaseType::ECMASCRIPT_OBJECT_TYPE, TaggedValue::TAG_OBJECT + 1, true);
}

TEST_F(CodegenEcmaTest, CompareAnyTypeInstObjectFalse)
{
    TestCompareAnyType(AnyBaseType::ECMASCRIPT_OBJECT_TYPE, TaggedValue::TAG_INT, false);
}

TEST_F(CodegenEcmaTest, CompareAnyTypeInstStringFalse)
{
    // Not implemented.
    TestCompareAnyType(AnyBaseType::ECMASCRIPT_STRING_TYPE, TaggedValue::TAG_OBJECT + 1, false);
}

TEST_F(CodegenEcmaTest, CompareAnyTypeInstUndefinedTrue)
{
    TestCompareAnyType(AnyBaseType::ECMASCRIPT_UNDEFINED_TYPE, TaggedValue::VALUE_UNDEFINED, true);
}

TEST_F(CodegenEcmaTest, CompareAnyTypeInstUndefinedFalse)
{
    TestCompareAnyType(AnyBaseType::ECMASCRIPT_UNDEFINED_TYPE, TaggedValue::TAG_INT, false);
}

template <typename T>
void CodegenEcmaTest::CastAnyTypeValue(AnyBaseType boxedType, TaggedValue boxedValue, T expectedUnboxedValue)
{
    auto graph = GetGraph();
    graph->SetDynamicMethod();
    GRAPH(graph)
    {
        CONSTANT(0, boxedValue.GetRawData()).any();

        BASIC_BLOCK(2, -1)
        {
            INST(2, Opcode::CastAnyTypeValue).AnyType(boxedType).Inputs(0);
            INS(2).SetType(AnyBaseTypeToDataType(boxedType));
            INS(2).SetFlag(inst_flags::Flags::NO_CSE);
            INS(2).SetFlag(inst_flags::Flags::NO_HOIST);
            INST(3, Opcode::Return).Inputs(2);
            INS(3).SetType(AnyBaseTypeToDataType(boxedType));
        }
    }

    CheckReturnValue(graph, expectedUnboxedValue);
}

TEST_F(CodegenEcmaTest, DISABLED_CastAnyTypeValueInstNull)
{
    // Unclear way for using should be fixed after real example.
    CastAnyTypeValue(AnyBaseType::ECMASCRIPT_NULL_TYPE, TaggedValue::Null(), 0);
}

TEST_F(CodegenEcmaTest, DISABLED_CastAnyTypeValueInstUndefined)
{
    // Unclear way for using should be fixed after real example.
    CastAnyTypeValue(AnyBaseType::ECMASCRIPT_UNDEFINED_TYPE, TaggedValue::Undefined(), 0);
}

TEST_F(CodegenEcmaTest, CastAnyTypeValueInstFalse)
{
    CastAnyTypeValue(AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE, TaggedValue::False(), false);
}

TEST_F(CodegenEcmaTest, CastAnyTypeValueInstTrue)
{
    CastAnyTypeValue(AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE, TaggedValue::True(), true);
}

TEST_F(CodegenEcmaTest, CastAnyTypeValueInstInt)
{
    constexpr int32_t PAYLOAD = 42;
    auto value = TaggedValue(PAYLOAD);

    ASSERT_TRUE(value.IsInt());
    ASSERT_TRUE(value.GetInt() == PAYLOAD);
    CastAnyTypeValue(AnyBaseType::ECMASCRIPT_INT_TYPE, value, PAYLOAD);
}

TEST_F(CodegenEcmaTest, CastAnyTypeValueInstDouble)
{
    constexpr double PAYLOAD = 42.0;
    auto value = TaggedValue(PAYLOAD);

    ASSERT_TRUE(value.IsDouble());
    ASSERT_TRUE(value.GetDouble() == PAYLOAD);
    CastAnyTypeValue(AnyBaseType::ECMASCRIPT_DOUBLE_TYPE, value, PAYLOAD);
}

TEST_F(CodegenEcmaTest, CastAnyTypeValueInstObject)
{
    // As long as tagging remains the same for all heap objects (strings, array, objects, etc.),
    // this single test is enough.
    const auto *payloadPtr = reinterpret_cast<ObjectHeader *>(0xbee8);
    const auto payloadInt = reinterpret_cast<int64_t>(payloadPtr);
    auto value = TaggedValue(payloadPtr);

    ASSERT_TRUE(value.IsHeapObject());
    ASSERT_TRUE(value.GetHeapObject() == payloadPtr);
    ASSERT_TRUE(reinterpret_cast<uintptr_t>(value.GetHeapObject()) == static_cast<uintptr_t>(payloadInt));
    CastAnyTypeValue(AnyBaseType::ECMASCRIPT_OBJECT_TYPE, value, payloadInt);
}

template <typename T>
void CodegenEcmaTest::CastValueToAnyType(T unboxedValue, AnyBaseType boxedType, uint64_t expectedBoxedValue)
{
    auto graph = GetGraph();
    graph->SetDynamicMethod();
    GRAPH(graph)
    {
        CONSTANT(0, unboxedValue);
        BASIC_BLOCK(2, -1)
        {
            INST(2, Opcode::CastValueToAnyType).any().AnyType(boxedType).Inputs(0);
            INS(2).SetFlag(inst_flags::Flags::NO_CSE);
            INS(2).SetFlag(inst_flags::Flags::NO_HOIST);
            INST(3, Opcode::Return).any().Inputs(2);
        }
    }

    CheckReturnValue(graph, expectedBoxedValue);
}

TEST_F(CodegenEcmaTest, DISABLED_CastValueToAnyTypeInstNull)
{
    // Unclear way for using should be fixed after real example.
    CastValueToAnyType(0, AnyBaseType::ECMASCRIPT_NULL_TYPE, TaggedValue::Null().GetRawData());
}

TEST_F(CodegenEcmaTest, DISABLED_CastValueToAnyTypeInstUndefined)
{
    // Unclear way for using should be fixed after real example.
    CastValueToAnyType(0, AnyBaseType::ECMASCRIPT_UNDEFINED_TYPE, TaggedValue::Undefined().GetRawData());
}

TEST_F(CodegenEcmaTest, CastValueToAnyTypeInstFalse)
{
    CastValueToAnyType(false, AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE, TaggedValue::False().GetRawData());
}

TEST_F(CodegenEcmaTest, CastValueToAnyTypeInstTrue)
{
    CastValueToAnyType(true, AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE, TaggedValue::True().GetRawData());
}

TEST_F(CodegenEcmaTest, CastValueToAnyTypeInstInt)
{
    constexpr int32_t PAYLOAD = 42;
    auto value = TaggedValue(PAYLOAD);

    ASSERT_TRUE(value.IsInt());
    ASSERT_TRUE(value.GetInt() == PAYLOAD);
    CastValueToAnyType(PAYLOAD, AnyBaseType::ECMASCRIPT_INT_TYPE, value.GetRawData());
}

TEST_F(CodegenEcmaTest, CastValueToAnyTypeInstDouble)
{
    constexpr double PAYLOAD = 42.0;
    auto value = TaggedValue(PAYLOAD);

    ASSERT_TRUE(value.IsDouble());
    ASSERT_TRUE(value.GetDouble() == PAYLOAD);
    CastValueToAnyType(PAYLOAD, AnyBaseType::ECMASCRIPT_DOUBLE_TYPE, value.GetRawData());
}

TEST_F(CodegenEcmaTest, CastValueToAnyTypeInstObject)
{
    // As long as tagging remains the same for all heap objects (strings, array, objects, etc.),
    // this single test is enough.
    const auto *payloadPtr = reinterpret_cast<ObjectHeader *>(0xbee8);
    const auto payloadInt = reinterpret_cast<int64_t>(payloadPtr);
    auto value = TaggedValue(payloadPtr);

    ASSERT_TRUE(value.IsHeapObject());
    ASSERT_TRUE(value.GetHeapObject() == payloadPtr);
    ASSERT_TRUE(reinterpret_cast<uintptr_t>(value.GetHeapObject()) == static_cast<uintptr_t>(payloadInt));
    CastValueToAnyType(payloadInt, AnyBaseType::ECMASCRIPT_OBJECT_TYPE, value.GetRawData());
}

void CodegenEcmaTest::TestAnyTypeCheck(AnyBaseType anyType, uint64_t val)
{
    auto graph = CreateEmptyGraph();
    graph->SetDynamicMethod();
#ifndef NDEBUG
    graph->SetDynUnitTestFlag();
#endif
    GRAPH(graph)
    {
        CONSTANT(0, val).any();
        CONSTANT(1, 1);

        BASIC_BLOCK(2, -1)
        {
            INST(4, Opcode::SaveState).Inputs(0).SrcVregs({0});
            INST(2, Opcode::AnyTypeCheck).b().AnyType(anyType).Inputs(0, 4);
            INST(3, Opcode::Return).b().Inputs(1);
        }
    }

    CheckReturnValue(graph, 1);
}

TEST_F(CodegenEcmaTest, AnyTypeCheckInstBoolTrue1)
{
    TestAnyTypeCheck(AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE, TaggedValue::VALUE_TRUE);
}

TEST_F(CodegenEcmaTest, AnyTypeCheckInstBoolTrue2)
{
    TestAnyTypeCheck(AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE, TaggedValue::VALUE_FALSE);
}

TEST_F(CodegenEcmaTest, AnyTypeCheckInstDoubleTrue)
{
    TestAnyTypeCheck(AnyBaseType::ECMASCRIPT_DOUBLE_TYPE, TaggedValue::DOUBLE_ENCODE_OFFSET);
}

TEST_F(CodegenEcmaTest, AnyTypeCheckInstIntTrue)
{
    TestAnyTypeCheck(AnyBaseType::ECMASCRIPT_INT_TYPE, TaggedValue::TAG_INT);
}

TEST_F(CodegenEcmaTest, AnyTypeCheckInstNullTrue)
{
    TestAnyTypeCheck(AnyBaseType::ECMASCRIPT_NULL_TYPE, TaggedValue::VALUE_NULL);
}

TEST_F(CodegenEcmaTest, AnyTypeCheckInstObjectTrue)
{
    TestAnyTypeCheck(AnyBaseType::ECMASCRIPT_OBJECT_TYPE, TaggedValue::TAG_OBJECT + 1);
}

TEST_F(CodegenEcmaTest, AnyTypeCheckInstUndefinedTrue)
{
    TestAnyTypeCheck(AnyBaseType::ECMASCRIPT_UNDEFINED_TYPE, TaggedValue::VALUE_UNDEFINED);
}

TEST_F(CodegenEcmaTest, AnyTypeCheckInstCommonUndefined)
{
    TestAnyTypeCheck(AnyBaseType::UNDEFINED_TYPE, TaggedValue::VALUE_FALSE);
    TestAnyTypeCheck(AnyBaseType::UNDEFINED_TYPE, TaggedValue::VALUE_TRUE);
    TestAnyTypeCheck(AnyBaseType::UNDEFINED_TYPE, TaggedValue::DOUBLE_ENCODE_OFFSET);
    TestAnyTypeCheck(AnyBaseType::UNDEFINED_TYPE, TaggedValue::TAG_INT);
    TestAnyTypeCheck(AnyBaseType::UNDEFINED_TYPE, TaggedValue::TAG_OBJECT + 1);
    TestAnyTypeCheck(AnyBaseType::UNDEFINED_TYPE, TaggedValue::VALUE_NULL);
    TestAnyTypeCheck(AnyBaseType::UNDEFINED_TYPE, TaggedValue::VALUE_UNDEFINED);
}
// NOLINTEND(readability-magic-numbers)

}  // namespace ark::compiler
